
/*
    this file is for os functions
    semaphore, mutex, task, timer, sleep
*/

#include <linux/version.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <mqueue.h>
#include <semaphore.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/stat.h>

typedef void* wl_ext_sem;
typedef void* wl_ext_mutex;
typedef void* wl_ext_task;
typedef void* wl_ext_timer;
typedef void* wl_ext_queue;

// invalid handle
#define WL_EXT_INVALID_HANDLE NULL

// wait forever, make sem and mutex wait in blocked mode
#define WL_EXT_WAIT_FOREVER -1

enum {

    EXT_TASK_PRIORITY_LOW = 0, // low than system normal
    EXT_TASK_PRIORITY_NORMAL = 1, // normal as system normal
    EXT_TASK_PRIORITY_HIGH = 2, // high than system normal
    EXT_TASK_PRIORITY_HIGHEST = 3 // highest
};

/**
 * timer callback
 * timer: timer handle
 * pArg: user data
 */
typedef void (*wl_ext_timer_callback)(wl_ext_timer timer, void *pArg);

/**
 * task callback
 * pArg: user data
 */
typedef void (*wl_ext_task_callback)(void *pArg);



/**
 * create mutex
 * @param mutex mutex handle
 * @param name mutex name
 * @return 0 on success, -1 on fail
 */
int wl_ext_mutex_create(wl_ext_mutex* mutex, const char* name) {
    pthread_mutex_t *new_mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    if (new_mutex == NULL) {
        return -1;
    }

    if (pthread_mutex_init(new_mutex, NULL) != 0) {
        free(new_mutex);
        return -1;
    }

    *mutex = (wl_ext_mutex)new_mutex;
    return 0;
}

/**
 * lock mutex
 * @param mutex mutex handle
 * @param timeout timeout in ms, -1 for wait forever
 * @return 0 on success, -1 on timeout, other < 0 on fail
 */
int wl_ext_mutex_lock(wl_ext_mutex mutex, int timeout) {
    int ret;
    pthread_mutex_t *native_mutex = (pthread_mutex_t *)mutex;

    if (timeout == WL_EXT_WAIT_FOREVER) {
        ret = pthread_mutex_lock(native_mutex);
    } else {
        struct timespec ts;
        ts.tv_sec = timeout / 1000;
        ts.tv_nsec = 0;

        ret = pthread_mutex_timedlock(native_mutex, &ts);
        if (ret == ETIMEDOUT) {
            return -1;
        }
    }

    return (ret == 0) ? 0 : -1;

}

/**
 * unlock mutex
 * @param mutex mutex handle
 * @return 0 on success, -1 on fail
 */
int wl_ext_mutex_unlock(wl_ext_mutex mutex) {
    int ret;
    pthread_mutex_t *native_mutex = (pthread_mutex_t *)mutex;

    ret = pthread_mutex_unlock(native_mutex);
    return (ret == 0) ? 0 : -1;
}

/**
 * destroy mutex
 * @param mutex mutex handle
 */
void wl_ext_mutex_destroy(wl_ext_mutex mutex) {
    pthread_mutex_t *native_mutex = (pthread_mutex_t *)mutex;
    pthread_mutex_destroy(native_mutex);
    free(mutex);
}


/**
 * create semaphore
 * @param sem semaphore handle
 * @param name semaphore name
 * @param initValue initial value
 * @return 0 on success, -1 on fail
 */
int wl_ext_sem_create(wl_ext_sem* sem, const char* name, int initValue) {
    sem_t *new_sem = (sem_t *)malloc(sizeof(sem_t));
    if (new_sem == NULL) {
        return -1;
    }

    if (sem_init(new_sem, 0, initValue) != 0) {
        free(new_sem);
        return -1;
    }

    *sem = (wl_ext_sem)new_sem;
    return 0;

}

/**
 * wait semaphore
 * @param sem semaphore handle
 * @param timeout timeout in ms, -1 for wait forever
 * @return 0 on success, -1 on timeout, other < 0 on fail
 */
int wl_ext_sem_wait(wl_ext_sem sem, int timeout) {
    struct timespec ts;
    int ret;
    sem_t *native_sem = (sem_t *)sem;

    if (timeout == WL_EXT_WAIT_FOREVER) {
        ret = sem_wait(native_sem);
    } else {
        ts.tv_sec = timeout / 1000;
        ts.tv_nsec = 0;

        ret = sem_timedwait(native_sem, &ts);
        if (ret == ETIMEDOUT) {
            return -1;
        }
    }

    return (ret == 0) ? 0 : -1;

}

/**
 * post semaphore
 * @param sem semaphore handle
 * @return 0 on success, -1 on fail 
 */
int wl_ext_sem_post(wl_ext_sem sem) {
    sem_t *native_sem = (sem_t *)sem;
    return (sem_post(native_sem) == 0) ? 0 : -1;

}

/**
 * destroy semaphore
 * @param sem semaphore handle
 */
void wl_ext_sem_destroy(wl_ext_sem sem) {
    sem_t *native_sem = (sem_t *)sem;
    sem_destroy(native_sem);
    free(native_sem);

}


typedef struct {
    timer_t native_timer;
    wl_ext_timer_callback callback;
    void *pContext;
}wl_ext_timer_impl;

void timer_handler(union sigval sg){
    /* data */
    wl_ext_timer_impl *impl = (wl_ext_timer_impl *)sg.sival_ptr;
    if (impl->callback) {
        impl->callback(impl, impl->pContext);
    }
}

/**
 * create timer
 * @param timer timer handle
 * @param name timer name
 * @return 0 on success, -1 on fail
 */
int wl_ext_timer_create(wl_ext_timer* timer, const char* name) {
    wl_ext_timer_impl *impl = (wl_ext_timer_impl *)malloc(sizeof(wl_ext_timer_impl));
    if (!impl) {
        return -1;
    }
    memset(impl, 0, sizeof(wl_ext_timer_impl));

    struct sigevent sev;
    memset(&sev, 0, sizeof(struct sigevent));
    sev.sigev_notify = SIGEV_THREAD; // 在新线程中执行
    sev.sigev_notify_function = timer_handler; // 到期执行的函数
    sev.sigev_notify_attributes = NULL;
    sev.sigev_value.sival_ptr = impl;


    if (timer_create(CLOCK_REALTIME, &sev, &impl->native_timer) != 0) {
        free(impl);
        return -1;
    }

    *timer = impl;
    return 0;
}

/**
 * start timer
 * @param timer timer handle
 * @param repeated repeat or not
 * @param interval interval in ms
 * @param callback timer callback
 * @param pArg user data
 * @return 0 on success, -1 on fail
 */
int wl_ext_timer_start(wl_ext_timer timer, int repeated, int interval, wl_ext_timer_callback callback, void* pArg) {
    wl_ext_timer_impl *impl = (wl_ext_timer_impl *)timer;
    impl->callback = callback;
    impl->pContext = pArg;

    struct itimerspec ts;
    memset(&ts, 0, sizeof(struct itimerspec));
    ts.it_value.tv_sec = interval / 1000;
    ts.it_value.tv_nsec = ((interval * 1000) % 1000000) * 1000;

    if (repeated) {
        ts.it_interval.tv_sec = interval / 1000;;
        ts.it_interval.tv_nsec = ((interval * 1000) % 1000000) * 1000;
    }else {
        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;
    }

    if (timer_settime(impl->native_timer, 0, &ts, NULL) != 0) {
        return -1;
    }

    return 0;
}

/**
 * stop timer
 * @param timer timer handle
 * @return 0 on success, -1 on fail 
 */
int wl_ext_timer_stop(wl_ext_timer timer) {
    wl_ext_timer_impl *impl = (wl_ext_timer_impl *)timer;
    struct itimerspec ts;
    ts.it_value.tv_sec = 0;
    ts.it_value.tv_nsec = 0;
    ts.it_interval.tv_sec = 0;
    ts.it_interval.tv_nsec = 0;

    if (timer_settime(impl->native_timer, 0, &ts, NULL) != 0) {
        return -1;
    }

    return 0;

}

/**
 * destroy timer
 * @param timer timer handle
 */
void wl_ext_timer_destroy(wl_ext_timer timer) {
    wl_ext_timer_impl *impl = (wl_ext_timer_impl *)timer;
    if (timer_delete(impl->native_timer) != 0) {
        
    }
    free(impl);
}

typedef struct {
    const char *pName;
    pthread_t thread;
    wl_ext_task_callback callback;
    void *pArg;
}wl_ext_task_impl;


void *task_handler(void *pArg) {
    wl_ext_task_impl *impl = (wl_ext_task_impl *)pArg;

    pthread_setname_np(pthread_self(), impl->pName);

    if (impl->callback) {
        impl->callback(impl->pArg);
    }

    return NULL;
}

/**
 * create task
 * @param task task handle
 * @param name task name
 * @param stackSize task stack size
 * @param priority task priority, 0: low than system normal, 1: normal as system normal, 2: high than system normal
 * @param callback task callback
 * @param pArg user data
 * @return 0 on success, -1 on fail
 */
int wl_ext_task_create(wl_ext_task* task, const char* name, int stackSize, int priority, wl_ext_task_callback callback, void* pArg) {
    wl_ext_task_impl *impl = (wl_ext_task_impl *)malloc(sizeof(wl_ext_task_impl));
    if (impl == NULL) {
        return -1;
    }

    impl->pName = name;
    impl->callback = callback;
    impl->pArg = pArg;

    int ret;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    struct sched_param param;
    switch (priority) {
        case EXT_TASK_PRIORITY_LOW:
            param.sched_priority = sched_get_priority_min(SCHED_OTHER);
            break;
        case EXT_TASK_PRIORITY_NORMAL:
            param.sched_priority = sched_get_priority_min(SCHED_OTHER) + (sched_get_priority_max(SCHED_OTHER) - sched_get_priority_min(SCHED_OTHER)) / 2;
            break;
        case EXT_TASK_PRIORITY_HIGH:
            param.sched_priority = sched_get_priority_max(SCHED_OTHER);
            break;
    }
    pthread_attr_setschedparam(&attr, &param);
    ret = pthread_create(&impl->thread, &attr, task_handler, impl);
    pthread_attr_destroy(&attr);

    if (ret != 0) {
        free(impl);
        return -1;
    }

    *task = (wl_ext_task)impl;

    return 0;
}

/**
 * destroy task
 * @param task task handle
 * @return 0 on success, -1 on fail
 */
int wl_ext_task_destroy(wl_ext_task task) {
    wl_ext_task_impl *impl = (wl_ext_task_impl *)task;
    free(impl);
    return 0;
}

/**
 * join task
 * @param task task handle
 * @return 0 on success, -1 on fail 
 */
int wl_ext_task_join(wl_ext_task task) {
    wl_ext_task_impl *impl = (wl_ext_task_impl *)task;
    int ret = pthread_join(impl->thread, NULL);
    if (ret != 0) {
        return -1;
    }
    return 0;

}

typedef struct {
    const char *pName;
    pthread_mutex_t lock;
    sem_t sem;
    void* msgList[1000];
}wl_ext_queue_impl;

/**
 * create queue
 * @param pQueue queue handle
 * @param name queue name
 * @param maxCount max message count
 * @param extra user data
 * @return 0 on success, -1 on fail
 */
int wl_ext_queue_create(wl_ext_queue *pQueue, const char *name, int maxCount, void* extra) {
    wl_ext_queue_impl *impl = (wl_ext_queue_impl *)malloc(sizeof(wl_ext_queue_impl));
    if (!impl) {
        return -1;
    }
    impl->pName = name;
    pthread_mutex_init(&impl->lock, NULL);

    sem_init(&impl->sem, 0, 0);

    *pQueue = (wl_ext_queue)impl;

    return 0;
}

/**
 * get message from queue
 * @param queue queue handle
 * @param pMsg message buffer
 * @return 0 on success, -1 on timeout, other < 0 on fail 
 */
int wl_ext_queue_get(wl_ext_queue queue, void** pMsg) {
    wl_ext_queue_impl *impl = (wl_ext_queue_impl *)queue;
    pthread_mutex_lock(&impl->lock);
    if (impl->msgList[0] != NULL) {
        *pMsg = impl->msgList[0];
        memmove(&impl->msgList[0], &impl->msgList[1], sizeof(void*) * (sizeof(impl->msgList) / sizeof(void*) - 1));
        pthread_mutex_unlock(&impl->lock);
        return 0;
    }else {
        pthread_mutex_unlock(&impl->lock);
        int ret = sem_wait(&impl->sem);
        if (ret != 0) {
            return -1;
        }
        pthread_mutex_lock(&impl->lock);
        *pMsg = impl->msgList[0];
        memmove(&impl->msgList[0], &impl->msgList[1], sizeof(void*) * (sizeof(impl->msgList) / sizeof(void*) - 1));
        pthread_mutex_unlock(&impl->lock);
        return 0;
    }
    pthread_mutex_unlock(&impl->lock);
    return -1;
}

/**
 * put message to queue
 * @param queue queue handle
 * @param pMsg message buffer
 * @return 0 on success, -1 on fail
 * 
 */
int wl_ext_queue_put(wl_ext_queue queue, void* pMsg) {
    wl_ext_queue_impl *impl = (wl_ext_queue_impl *)queue;
    int emptyBefore = 0;
    int filled = 0;
    pthread_mutex_lock(&impl->lock);
    if (impl->msgList[0] == NULL) {
        emptyBefore = 1;
    }

    for (int i = 0; i < 1000; i++) {
        if (impl->msgList[i] == NULL) {
            impl->msgList[i] = pMsg;
            filled = 1;
            break;
        }
    }
    pthread_mutex_unlock(&impl->lock);

    if (emptyBefore && filled) {
        sem_post(&impl->sem);
    }
    
    return filled ? 0 : -1; // Queue is full
}

/**
 * destroy queue
 * @param queue queue handle
 * @return 0 on success, -1 on fail
 *
 */
int wl_ext_queue_destroy(wl_ext_queue queue) {
    wl_ext_queue_impl *impl = (wl_ext_queue_impl *)queue;
    if (impl == NULL) {
        return -1; // Invalid queue
    }

    pthread_mutex_destroy(&impl->lock);
    free(impl);
    return 0;
}

/**
 * sleep
 * @param msec sleep time in ms
 * @return 0 on success, -1 on fail
 */
int wl_ext_sleep(int msec) {
    usleep(msec * 1000);
    return 0;
}
